#pragma once
#include <deque>
#include <cstdint>
#include <random>

#include <spdlog/spdlog.h>

#include "zmq.hpp"

using spdlog::info;

using zmsg = zmq::message_t;

class zframe
{
public:
	zframe() {};
	~zframe() {};

public:
	zframe(zframe&& rhs) :
		m_msgs(std::move(rhs.m_msgs))
	{
	}

	zframe& operator=(zframe&& rhs) ZMQ_NOTHROW
	{
		m_msgs = std::move(rhs.m_msgs);
		return *this;
	}

public:
	void clear() { m_msgs.clear(); }

public:
	size_t size() const { return m_msgs.size(); }

	bool is_empty() const { return m_msgs.empty(); }

	const zmq::message_t& operator[](size_t index) const {
		return m_msgs[index];
	}

	zmq::message_t& operator[](size_t index) {
		return m_msgs[index];
	}

public:
	const zmq::message_t& front() const {
		return m_msgs.front();
	}

	zmq::message_t& front() {
		return m_msgs.front();
	}

	const zmq::message_t& back() const {
		return m_msgs.back();
	}

	zmq::message_t& back() {
		return m_msgs.back();
	}

public:
	void push_back(const std::string& msg) {
		m_msgs.emplace_back(msg.data(), msg.size());
	}

	void push_back(const zmq::message_t& msg) {
		m_msgs.emplace_back(msg.data(), msg.size());
	}

	void push_back(zmq::message_t&& msg) {
		m_msgs.emplace_back(std::move(msg));
	}

	void push_back(int32_t value) {
		m_msgs.emplace_back(&value, sizeof(value));
	}

	void push_back() {
		m_msgs.emplace_back();
	}

public:
	void push_front(const std::string& msg) {
		m_msgs.emplace_front(msg.data(), msg.size());
	}

	void push_front(const zmq::message_t& msg) {
		m_msgs.emplace_front(msg.data(), msg.size());
	}

	void push_front(zmq::message_t&& msg) {
		m_msgs.emplace_front(std::move(msg));
	}

	void push_front(int32_t value) {
		m_msgs.emplace_front(&value, sizeof(value));
	}

	void push_front() {
		m_msgs.emplace_front();
	}

public:
	zmsg pop_back() {
		zmsg msg = std::move(m_msgs.back());
		m_msgs.pop_back();
		return msg;
	}

	zmsg pop_front() {
		zmsg msg = std::move(m_msgs.front());
		m_msgs.pop_front();
		return msg;
	}

public:
	zframe copy() const{
		zframe frame;
		for (const zmq::message_t& msg : m_msgs)
		{
			frame.push_back(msg);
		}
		return frame;
	}

public:
	void dump() {
		info("-------------------------------------");
		for (zmq::message_t& msg : m_msgs)
		{
			info(dump_msg(msg));
		}
		info("-------------------------------------");
	}

private:
	std::string dump_msg(zmq::message_t& msg)
	{
		std::stringstream ss;

		char* data = msg.data<char>();
		bool is_text = true;
		for (size_t i = 0; i < msg.size(); i++) {
			if (data[i] < 32 || data[i] > 127)
				is_text = false;
		}

		ss << "[" << std::setfill('0') << std::setw(3) << msg.size() << "] ";
		for (size_t i = 0; i < msg.size(); i++) {
			if (is_text)
				ss << (char)data[i];
			else
				ss << std::setfill('0') << std::setw(2)
				<< std::hex << (unsigned int)(unsigned char)data[i];
		}

		return ss.str();
	}

private:
	std::deque<zmq::message_t> m_msgs;
};

class zsocket
{
public:
	zsocket(zmq::context_t& ctx, zmq::socket_type type):
		m_socket(ctx, type) 
	{
	}

	zsocket(zsocket&& rhs) :
		m_socket(std::move(rhs.m_socket)) 
	{
	}

public:
	std::string set_random_id()
	{
		std::random_device rd;
		std::uniform_int_distribution<int> dist(0, 10000);
		std::string id = fmt::format("{:04X}-{:04X}", dist(rd), dist(rd));
		m_socket.setsockopt(ZMQ_IDENTITY, id.data(), id.size());

		return id;
	}

public:
	zsocket& operator=(zsocket&& rhs)
	{
		std::swap(m_socket, rhs.m_socket);
		return *this;
	}

	inline operator void* () { 
		return m_socket;
	}

	inline operator void const* () const { 
		return m_socket;
	}

public:
	void bind(std::string const& addr) 
	{ 
		m_socket.bind(addr.c_str());
	}

	void connect(std::string const& addr)
	{
		m_socket.connect(addr.c_str());
	}

	void close()
	{
		m_socket.close();
	}

public:
	void set_linger(int linger)
	{
		m_socket.setsockopt(ZMQ_LINGER, &linger, sizeof(linger));
	}

public:
	void send(zframe& frame) {
		if (frame.size() == 0) {
			return;
		}

		for (size_t i = 0; i < frame.size()-1; ++i)
		{
			m_socket.send(frame[i], ZMQ_SNDMORE);
		}
		m_socket.send(frame.back());
	}

	void send(zframe&& frame) {
		send(frame);
	}

	void send(const std::string& msg) {
		m_socket.send(msg.data(), msg.size());
	}

	void send(zmq::message_t msg)
	{
		m_socket.send(msg);
	}

public:
	bool recv(zframe& frame) {
		frame.clear();
		zmq::message_t msg;
		while (true)
		{
			if (!m_socket.recv(&msg))
			{
				return false;
			}

			frame.push_back(std::move(msg));
			if (!frame.back().more())
			{
				break;
			}
		}
		return true;
	}

private:
	zmq::socket_t m_socket;
};

inline bool operator==(const zmq::message_t& msg, const std::string& string)
{
	std::string lhs(msg.data<char>(), msg.size());
	return lhs == string;
}

inline bool operator==(const zmq::message_t& msg, const char* string)
{
	std::string lhs(msg.data<char>(), msg.size());
	return lhs == string;
}

int32_t to_int32(const zmq::message_t& msg) {
	if (msg.size() != sizeof(int32_t))
	{
		throw std::runtime_error("invalid transform");
	}

	return *msg.data<int32_t>();
}

std::string to_hex_string(zmsg& msg)
{
	char* data = msg.data<char>();
	std::stringstream ss;
	for (size_t i = 0; i < msg.size(); i++) {
		ss << std::setfill('0') << std::setw(2)
			<< std::hex << (unsigned int)(unsigned char)data[i];
	}
	return ss.str();
}

std::string to_string(zmsg& msg) {
	return std::string(msg.data<char>(), msg.size());
}